﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ODataRegression.Framework.Core;
using System.Xml;
using ODataRegression.Framework.Core.TestOM;
using System.IO;
using System.Threading;
using ODataTestFrameworkCore.shared;
using System.Diagnostics;
using System.Net;
using ODataRegression.Framework.Core.Factories;
namespace ODataRegression.Framework.Core.Shared
{
   public class TestEngine
    {
        private List<ODataTestBatch> MasterBatchList = new List<ODataTestBatch>();
        List<string> ServiceUrls = new List<string>();
       // List<string> HostList = null;
       // List<string> ServiceList = null;
      //  TestConfiguration config;
        List<ODataTest> TestList = new List<ODataTest>();
        Logger frameWorkLogger;
       private TimeSpan totalExecutionTime;

     
        public TestEngine(string testConfigName)
        {
            TestConfigReader.Intit(testConfigName);

            //ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
            //ServicePointManager.DefaultConnectionLimit = this.maxWorkerThreadCount + 5;

           // this.config = new TestConfiguration(testConfigName);
            frameWorkLogger = new Logger("Error.log");
           // HostList = config.HostList;
           // this.ServiceList = config.ServiceList;
            
            // string serviceURL;


            foreach (XmlNode root in TestConfigReader.ServicesEndPoints)
            {
                string BusinessName = string.Empty;
                string EntitySetsFilters = string.Empty;
                string URL = string.Empty;
                ODataServiceInformation serviceInfo = null;

                if (root.NodeType == XmlNodeType.Element)
                {
                    BusinessName = root.Attributes["BusinessName"].Value;
                    URL = root.Attributes["Url"].Value;

                    if (root.Attributes["EntitySetsFilters"] != null)
                    {
                        EntitySetsFilters = root.Attributes["EntitySetsFilters"].Value;
                    }


                   

                     serviceInfo = new ODataServiceInformation(BusinessName, EntitySetsFilters, URL);
                    //ServicesToTest.Add(BusinessName, URL);

                    try
                    {
                        //Add MetaData and Endpoint verificatn test here 
                        ODataEndPointTestFactory factoy = new ODataEndPointTestFactory(serviceInfo.ServiceURI, serviceInfo.ServiceBusinessName, null);
                        TestList.InsertRange(0, factoy.CreateTestList());

                        ODataMetadataTestFactory metadatafactoy = new ODataMetadataTestFactory(serviceInfo.ServiceURI, serviceInfo.ServiceBusinessName, null);
                        TestList.InsertRange(0, metadatafactoy.CreateTestList());

                        MasterBatchList.Add(new ODataTestBatch(serviceInfo));
                    }
                    catch (Exception e)
                    {
                        frameWorkLogger.LogInformation("Error:");
                        frameWorkLogger.LogInformation(string.Format("serviceRoot={0},    serviceName={1}", serviceInfo.ServiceBusinessName, serviceInfo.ServiceURI));
                        frameWorkLogger.LogException(e);

                        ConsoleLogger.LogException(e);
                    }
                   
                }
            }
           

            foreach (ODataTestBatch batch in this.MasterBatchList)
            {
                TestList.AddRange(batch.TestList);
            }
        }

        int outputRefreshInterval;
        int availbeWorkerThread;
        int currentWorkerThreadCount;
        int currentIOThreadsCount;
        int minThreadsCount;
        int maxWorkerThreadCount;
        public TimeSpan ExecuteTest()
        {
            DateTime StartTime=DateTime.Now;

           
            ThreadPool.GetMaxThreads(out currentWorkerThreadCount, out currentIOThreadsCount);
          
             minThreadsCount = Convert.ToInt32(TestConfigReader.TestEngineSettings[TestEngineSettingsTypes.MinExecutionThreads]);
             maxWorkerThreadCount = Convert.ToInt32(TestConfigReader.TestEngineSettings[TestEngineSettingsTypes.MaxExecutionThreads]);
            ThreadPool.SetMinThreads(minThreadsCount, currentIOThreadsCount);
            ThreadPool.SetMaxThreads(maxWorkerThreadCount, currentIOThreadsCount);
            
           
            ThreadPool.GetAvailableThreads(out availbeWorkerThread, out currentIOThreadsCount);
            
            //Need to start threads on random delay to avoide network overload 
            Random rand = new Random(20);
            foreach (ODataTest test in TestList)
            {
                ThreadPool.QueueUserWorkItem(test.Execute);
                //Randmization is required to prevent network overload
               //Thread.Sleep(rand.Next(100,500));
            }

            WaitForTestsToComplete();
            DateTime EndTIme = DateTime.Now;
            this.totalExecutionTime = EndTIme - StartTime;
            return this.totalExecutionTime;
        }

        void WaitForTestsToComplete()
        {
            
             outputRefreshInterval = Convert.ToInt32(TestConfigReader.TestEngineSettings[TestEngineSettingsTypes.OutputRefreshInterval]);
            do
            {
                Thread.Sleep(outputRefreshInterval);

            } while (!IsPoolIdle());
        }
        bool IsPoolIdle()
        {
           
            availbeWorkerThread = 0;
            currentIOThreadsCount = 0;

            ThreadPool.GetAvailableThreads(out availbeWorkerThread, out currentIOThreadsCount);
            int pendingExecution = GetTestPendingExecution();

            ConsoleLogger.LogMessage(string.Format("Test execution In progress, {0}/{1} executed, {2}/{3} Threads free",
               pendingExecution, TestList.Count, availbeWorkerThread, maxWorkerThreadCount));

            return (availbeWorkerThread == maxWorkerThreadCount);
        }
        private int GetTestPendingExecution()
        {
            //TODO need to handle in multithreaded way such as static counter in ODataTest class.
            int pending = TestList.Count;
            foreach (ODataTest item in TestList)
            {
                if (item.TestResult.testExecutionStatus!=TestExecutionStatus.Completed)
                {
                    --pending;
                }
            }
            return pending;
        }

        public void SaveResultAsXML(string fileName)
        {
            //Save Result to XML
            XmlDocument xDoc = new XmlDocument();

            // Processing Instructions 
            xDoc.AppendChild(xDoc.CreateXmlDeclaration("1.0", null, null));
            String PItext = "type='text/xsl' href='Result.xsl'";
            XmlProcessingInstruction newPI = xDoc.CreateProcessingInstruction("xml-stylesheet", PItext);
            xDoc.AppendChild(newPI);

            // Adding doc root
            XmlNode root = xDoc.AppendChild(xDoc.CreateElement("ROOT"));
            
            //Adding result node
            XmlNode Results = root.AppendChild(xDoc.CreateElement("Results"));

           

           /// Loading Execution result
            foreach (ODataTest result in TestList)
            {
                AddResultNode(Results, result);
            }

            //// Analysing Result 
            int TotalTestCount = 0;
            int Passed = 0;
            int Failed = 0;
            int Error = 0;

            XmlNodeList results = xDoc.SelectNodes("ROOT//Results/Result");
            if (results!=null)
            {
                TotalTestCount = results.Count;
            }
            results = xDoc.SelectNodes("ROOT/Results/Result[@TestResult='PASSED']");
            if (results != null)
            {
                Passed = results.Count;

            }
            results = xDoc.SelectNodes("ROOT/Results/Result[@TestResult='FAILED']");
            if (results != null)
            {
                Failed = results.Count;
                
            }

            Error = TotalTestCount - Passed - Failed;
                
           

            //Adding Test Summary Node
            XmlNode summaryNode = root.InsertBefore(xDoc.CreateElement("ExecutionSummary"),root.FirstChild);
            //root.AppendChild(summaryNode);

            //// Loading Summary attribute 
            XmlAttribute summaryAttrib;
            summaryAttrib = summaryNode.Attributes.Append(xDoc.CreateAttribute("ExecutionRunTime"));
            summaryAttrib.Value = string.Format("{0}:{1}:{2}:{3} hh:mm:ss:ms",this.totalExecutionTime.Hours,this.totalExecutionTime.Minutes,
                this.totalExecutionTime.Seconds,this.totalExecutionTime.Milliseconds); 


            summaryAttrib = summaryNode.Attributes.Append(xDoc.CreateAttribute("TotalTestCount"));
            summaryAttrib.Value = TotalTestCount.ToString();

            summaryAttrib = summaryNode.Attributes.Append(xDoc.CreateAttribute("Passed"));
            summaryAttrib.Value = Passed.ToString();

            summaryAttrib = summaryNode.Attributes.Append(xDoc.CreateAttribute("Failed"));
            summaryAttrib.Value = Failed.ToString();

            summaryAttrib = summaryNode.Attributes.Append(xDoc.CreateAttribute("Error"));
            summaryAttrib.Value = Error.ToString();

            xDoc.Save(fileName);


        }

        private static void AddResultNode(XmlNode xDoc, ODataTest test)
        {
            XmlNode result;

            result = xDoc.AppendChild(xDoc.OwnerDocument.CreateElement("Result"));
            foreach (KeyValuePair<string,string> item in test.ResultBag)
            {
                result.Attributes.Append(AddAttribute(result, item.Key, item.Value));
            }
        }

        private static XmlAttribute AddAttribute(XmlNode node, string Name, string value)
        {

            XmlAttribute tmpAtrib;
            tmpAtrib = node.OwnerDocument.CreateAttribute(Name);
            tmpAtrib.Value = value;
            return tmpAtrib;
        }
    }
}
